//Copyright (C) 2000-2006 Gnanasekaran Swaminathan <sekar@sekar.net>
//
//Permission is granted to use at your own risk and distribute this software
//in source and  binary forms provided  the above copyright notice and  this
//paragraph are  preserved on all copies.  This software is provided "as is"
//with no express or implied warranty.

#include <cstdarg>
#include <cstring>
#include <ctime>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <io.h>
#include <direct.h>

#include <windows.h>
#include "socklib.h"

namespace socklib {
  // the following two are defined in sockerror.cpp
  extern const char* sockerror(int err);
  extern const char* sockerror_detailed(int err);
}

void
socklib::stringf::vaddf(const char* fmt, va_list ap)
{
  stringf str;
  str.vsetf(fmt, ap);

  append(str);
}

void
socklib::stringf::vsetf(const char* fmt, va_list ap)
{
  std::string fmtstring("");
  int         prevchar = 0;

  while(*fmt) {
    int curchar = *fmt++;

    if (prevchar != '$') {
      if (curchar != '$')
        fmtstring.append(1, curchar);
      prevchar = curchar;
      continue;
    }

    switch(curchar) {
    case '$':
      fmtstring.append(1, '$');
      break;

    case 'e':
      {
        // insert err
        char* err = strerror(errno);
        if (err == 0) err = "";
        fmtstring.append(err);
      }
      break;

    case 'm':
      {
        // insert msg
        char msg[2048]; msg[0] = 0;
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
			      0,
			      GetLastError(),
			      0, 
			      msg,
			      2048,
			      0 
	      );
        size_t msglen = strlen(msg);
        while(msglen && isspace(msg[msglen-1])) msg[--msglen] = 0;

        fmtstring.append(msg);
      }
      break;

    case 'T':
      {
        time_t ctime = time(0);
        char   ct[26];
        strftime(ct, 26, "%Y-%m-%d %H:%M:%S", localtime(&ctime));

        fmtstring.append(ct);
      }
      break;

    case 'H':
      {
        char hostname[256];
        if (gethostname(hostname, 256) == SOCKET_ERROR)
          strcpy(hostname, "localhost");
        fmtstring.append(hostname);
      }
      break;

    case 's':
      {
        int err = WSAGetLastError();
        const char* errstr = sockerror(err);
        fmtstring.append(errstr);
      }
      break;

    case 'S':
      {
        int err = WSAGetLastError();
        const char* errstr = sockerror_detailed(err);
        fmtstring.append(errstr);
      }
      break;

    default:
      fmtstring.append(1, '$'); // put back the $
      fmtstring.append(1, curchar);
      break;
    }

    prevchar = 0;
  }

  tmpbuffer tmp(1000000);
  _vsnprintf(tmp.buffer, tmp.size, fmtstring.c_str(), ap);

  assign(tmp.buffer);
}

void
socklib::stringf::addf(const char* fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  vaddf(fmt, ap);
  va_end(ap);
}

void
socklib::stringf::setf(const char* fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  vsetf(fmt, ap);
  va_end(ap);
}

socklib::exception::exception(const char* fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  errmsg.vsetf(fmt, ap);
  va_end(ap);
}

void
socklib::exception::operator()(const char* fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  errmsg.vsetf(fmt, ap);
  va_end(ap);
}

void
socklib::exception::seterrf(const char* fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  errmsg.vaddf(fmt, ap);
  va_end(ap);
}

void
socklib::exception::vseterrf(const char* fmt, va_list ap)
{
  errmsg.vaddf(fmt, ap);
}

void
socklib::bytes::add(const char* v)
{
  const unsigned char* cv = (const unsigned char*)v;
  insert(end(), cv, cv + strlen(v));
}

void
socklib::bytes::addf(const char* fmt, ...)
{
  stringf buf;
 
  va_list ap;
  va_start(ap, fmt);
  buf.vsetf(fmt, ap);
  va_end(ap);

  add(buf.c_str());
}

socklib::connection::~connection()
{}

socklib::file_connection::file_connection(const char* filename)
{
  fd = _open(filename, _O_BINARY | _O_CREAT | _O_RDWR);
  if (fd == -1)
    throw socklib::exception("socklib::file_connection: unable to read file %s; $e", filename);
}

socklib::file_connection::~file_connection()
{
  if (fd != -1)
    _close(fd);
  fd = -1;
}

void
socklib::file_connection::write(const void* buf, size_t len)
{
  while(len) {
    int wr = _write(fd, buf, (unsigned int)len);
    if (wr == -1)
      throw socklib::exception("socklib::file_connection::send: unable to write; $e");
    len -= wr;
    buf  = (const char*)buf + len;
  }
}

size_t
socklib::file_connection::read(void* buf, size_t maxlen)
{
  int rd = _read(fd, buf, (unsigned int)maxlen);
  if (rd == -1)
    throw socklib::exception("socklib::file_connection::read: unable to write; $e");
  return rd;
}

void
socklib::buffer_connection::write(const void* buf, size_t len)
{
  buffer.insert(buffer.end(), (unsigned char*)buf, (unsigned char*)buf + len);
}

size_t
socklib::buffer_connection::read(void* buf, size_t maxlen)
{
  unsigned char* p = (unsigned char*)buf;

  size_t sz = buffer.size();
  if (maxlen > sz) maxlen = sz;

  bytes::iterator bi = buffer.begin();
  bytes::iterator ei = bi + maxlen;

  while(bi != ei) {
    *p++ = *bi;
    bi = buffer.erase(bi);
  }

  return maxlen;
}

socklib::connection_buffer::~connection_buffer()
{}

void
socklib::connection_buffer::put_int8(char v)
{
  put_byte((unsigned __int8)v);
}

void
socklib::connection_buffer::put_uint8(unsigned char v)
{
  put_byte((unsigned __int8)v);
}

void
socklib::connection_buffer::put_int16(short v)
{
  put_byte((unsigned __int8)(v >> 8));
  put_byte((unsigned __int8)v);
}

void
socklib::connection_buffer::put_uint16(unsigned short v)
{
  put_byte((unsigned __int8)(v >> 8));
  put_byte((unsigned __int8)v);
}

void
socklib::connection_buffer::put_int24(int v)
{
  put_byte((unsigned __int8)(v >> 16));
  put_byte((unsigned __int8)(v >> 8));
  put_byte((unsigned __int8)v);
}

void
socklib::connection_buffer::put_uint24(unsigned int v)
{
  put_byte((unsigned __int8)(v >> 16));
  put_byte((unsigned __int8)(v >> 8));
  put_byte((unsigned __int8)v);
}

void
socklib::connection_buffer::put_int32(int v)
{
  put_byte((unsigned __int8)(v >> 24));
  put_byte((unsigned __int8)(v >> 16));
  put_byte((unsigned __int8)(v >> 8));
  put_byte((unsigned __int8)v);
}

void
socklib::connection_buffer::put_uint32(unsigned int v)
{
  put_byte((unsigned __int8)(v >> 24));
  put_byte((unsigned __int8)(v >> 16));
  put_byte((unsigned __int8)(v >> 8));
  put_byte((unsigned __int8)v);
}

void
socklib::connection_buffer::put_int64(__int64 v)
{
  put_byte((unsigned __int8)(v >> 56));
  put_byte((unsigned __int8)(v >> 48));
  put_byte((unsigned __int8)(v >> 40));
  put_byte((unsigned __int8)(v >> 32));
  put_byte((unsigned __int8)(v >> 24));
  put_byte((unsigned __int8)(v >> 16));
  put_byte((unsigned __int8)(v >> 8));
  put_byte((unsigned __int8)v);
}

void
socklib::connection_buffer::put_uint64(unsigned __int64 v)
{
  put_byte((unsigned __int8)(v >> 56));
  put_byte((unsigned __int8)(v >> 48));
  put_byte((unsigned __int8)(v >> 40));
  put_byte((unsigned __int8)(v >> 32));
  put_byte((unsigned __int8)(v >> 24));
  put_byte((unsigned __int8)(v >> 16));
  put_byte((unsigned __int8)(v >> 8));
  put_byte((unsigned __int8)v);
}

void
socklib::connection_buffer::put_uint48(unsigned __int64 v)
{
  put_byte((unsigned __int8)(v >> 40));
  put_byte((unsigned __int8)(v >> 32));
  put_byte((unsigned __int8)(v >> 24));
  put_byte((unsigned __int8)(v >> 16));
  put_byte((unsigned __int8)(v >> 8));
  put_byte((unsigned __int8)v);
}

void
socklib::connection_buffer::put_buf(const void* v, size_t len)
{
  while(len) {
    size_t avail = 2048 - sbufend;

    if (avail == 0) {
      flush();
      continue;
    }

    if (avail > len) avail = len;

    memmove(sbuf+sbufend, v, avail);
    sbufend += (int)avail;
    v        = (unsigned char*)v + avail;
    len     -= avail;
  }
}

void
socklib::connection_buffer::putf(const char* fmt, ...)
{
  char buf[16384];
  va_list ap;
  va_start(ap, fmt);
  _vsnprintf(buf, 16384, fmt, ap);
  va_end(ap);

  put_str(buf);
}

void
socklib::connection_buffer::flush()
{
  con->write(sbuf+sbufbeg, sbufend-sbufbeg);
  sbufbeg = sbufend = 0;
}

void
socklib::connection_buffer::unget()
{
  if (rbufbeg > 0) rbufbeg--;
}

void
socklib::connection_buffer::trash(size_t exactlen)
{
  while(exactlen--) get_byte();
}

unsigned char
socklib::connection_buffer::get_byte()
{
  if (rbufbeg == rbufend) {
    rbufbeg = rbufend = 0;

    size_t rd = con->read(rbuf, 2048);
    if (rd == 0)
      throw socklib::exception("socklib::connection_buffer::get_byte: EOF reached.");

    rbufend += (unsigned int)rd;
  }

  return rbuf[rbufbeg++];
}

char
socklib::connection_buffer::get_int8()
{
  return (char)get_byte();
}

unsigned char
socklib::connection_buffer::get_uint8()
{
  return get_byte();
}

short
socklib::connection_buffer::get_int16()
{ 
  short v = 0;

  v |= (unsigned short)get_byte() << 8;
  v |= get_byte();

  return v;
}

unsigned short
socklib::connection_buffer::get_uint16()
{
  unsigned short v = 0;

  v |= (unsigned short)get_byte() << 8;
  v |= get_byte();

  return v;
}

int
socklib::connection_buffer::get_int24()
{
  int v = 0;
  v |= (unsigned int)get_byte() << 16;
  v |= (unsigned int)get_byte() << 8;
  v |= get_byte();

  return v;
}

unsigned int
socklib::connection_buffer::get_uint24()
{
  unsigned int v = 0;
  v |= (unsigned int)get_byte() << 16;
  v |= (unsigned int)get_byte() << 8;
  v |= get_byte();

  return v;
}

int
socklib::connection_buffer::get_int32()
{
  int v = 0;
  v |= (unsigned int)get_byte() << 24;
  v |= (unsigned int)get_byte() << 16;
  v |= (unsigned int)get_byte() << 8;
  v |= get_byte();

  return v;
}

unsigned int
socklib::connection_buffer::get_uint32()
{
  unsigned int v = 0;
  v |= (unsigned int)get_byte() << 24;
  v |= (unsigned int)get_byte() << 16;
  v |= (unsigned int)get_byte() << 8;
  v |= get_byte();

  return v;
}

__int64
socklib::connection_buffer::get_int64()
{
  __int64 v = 0;
  v |= (unsigned __int64)get_byte() << 56;
  v |= (unsigned __int64)get_byte() << 48;
  v |= (unsigned __int64)get_byte() << 40;
  v |= (unsigned __int64)get_byte() << 32;
  v |= (unsigned __int64)get_byte() << 24;
  v |= (unsigned __int64)get_byte() << 16;
  v |= (unsigned __int64)get_byte() << 8;
  v |= get_byte();

  return v;
}

unsigned __int64
socklib::connection_buffer::get_uint64()
{
  unsigned __int64 v = 0;
  v |= (unsigned __int64)get_byte() << 56;
  v |= (unsigned __int64)get_byte() << 48;
  v |= (unsigned __int64)get_byte() << 40;
  v |= (unsigned __int64)get_byte() << 32;
  v |= (unsigned __int64)get_byte() << 24;
  v |= (unsigned __int64)get_byte() << 16;
  v |= (unsigned __int64)get_byte() << 8;
  v |= get_byte();

  return v;
}

unsigned __int64
socklib::connection_buffer::get_uint48()
{
  unsigned __int64 v = 0;
  v |= (unsigned __int64)get_byte() << 40;
  v |= (unsigned __int64)get_byte() << 32;
  v |= (unsigned __int64)get_byte() << 24;
  v |= (unsigned __int64)get_byte() << 16;
  v |= (unsigned __int64)get_byte() << 8;
  v |= get_byte();

  return v;
}

void
socklib::connection_buffer::get_string(std::string& v, size_t exactlen)
{
  while(exactlen--)
    v.append(1, get_int8());
}

void
socklib::connection_buffer::get_line(char* v, size_t maxlen)
{
  char pc = 0;
  char* e = v + maxlen - 1;

  while(v < e) {
    char c = get_int8();
    *v++ = c;

    if (pc == '\r' && c == '\n') break; // found CRLF
    pc = c;
  }

  *v = 0;
}

void
socklib::connection_buffer::get_line(std::string& v)
{
  char pc = 0;
  char c  = 0;

  while(pc != '\r' || (c = get_int8()) != '\n') {
    v.append(1, c);
    pc = c;
  }
}

void
socklib::connection_buffer::get_buf(void* v, size_t exactlen)
{
  unsigned char* p = (unsigned char*)v;
  while(exactlen--)
    *p++ = get_byte();
}

std::string
socklib::get_current_image_name()
{
  char fullpath[MAX_PATH];

  if (GetModuleFileNameA(0, fullpath, MAX_PATH) == 0)
    return "unknown";

  char fname[_MAX_FNAME];
  _splitpath(fullpath,0, 0, fname, 0);

  return fname;
}

void
socklib::delete_directory(const char* directory)
{
  if (directory == 0 || directory[0] == 0) return;

  _unlink(directory);
  _rmdir(directory);

  if (_access(directory, 0) == -1) return;

  char path[1024];
  char* pathe = 0;
  strcpy(path, directory);
  pathe = path + strlen(path) - 1;
  if (*pathe != '\\') *++pathe = '\\';

  strcpy(++pathe, "*");

  struct _finddata_t filedata;
  intptr_t fh = _findfirst(path, &filedata);

  if (fh != -1L) {
    do {
      strcpy(pathe, filedata.name);
      if (filedata.attrib & _A_SUBDIR) {
        if (strcmp(filedata.name, ".") && strcmp(filedata.name, ".."))
          delete_directory(directory);
      } else
        _unlink(path);
    } while(_findnext(fh, &filedata) == 0);

    _findclose(fh);
  }

  _rmdir(directory);
}

std::string
socklib::fmtcomma(int val)
{
  int sign = val < 0 ? '-': 0;

  char buf[128];
  char* e = buf+127;
  *e-- = 0;

  char* p         = e;
  int   numdigits = 0;
  do {
    if (numdigits && numdigits % 3 == 0) *p-- = ',';
    *p-- = '0' + val%10;
    val /= 10;
    numdigits++;
  } while(val);

  if (sign) *p = sign;
  else p++;

  return std::string(p);
}

std::string
socklib::fmtcomma(unsigned int val)
{
  char buf[128];
  char* e = buf+127;
  *e-- = 0;

  char* p         = e;
  int   numdigits = 0;
  do {
    if (numdigits && numdigits % 3 == 0) *p-- = ',';
    *p-- = '0' + val%10;
    val /= 10;
    numdigits++;
  } while(val);

  return std::string(++p);
}

std::string
socklib::time2string(time_t t)
{
  if (t == 0) t = time(0);
  
  _tzset();

  char   tzsign = '+';
  struct tm tlocal = *localtime(&t);
  struct tm tgmt   = *gmtime(&t);
  if (tlocal.tm_year < tgmt.tm_year
      || tlocal.tm_year == tgmt.tm_year
         && tlocal.tm_mon < tgmt.tm_mon
      || tlocal.tm_year == tgmt.tm_year
         && tlocal.tm_mon == tgmt.tm_mon
         && tlocal.tm_mday < tgmt.tm_mday
      || tlocal.tm_year == tgmt.tm_year
         && tlocal.tm_mon == tgmt.tm_mon
         && tlocal.tm_mday == tgmt.tm_mday
         && tlocal.tm_hour < tgmt.tm_hour
      || tlocal.tm_year == tgmt.tm_year
         && tlocal.tm_mon == tgmt.tm_mon
         && tlocal.tm_mday == tgmt.tm_mday
         && tlocal.tm_hour == tgmt.tm_hour
         && tlocal.tm_min < tgmt.tm_min)
    tzsign = '-';

  time_t tzone = _timezone;
  if (tlocal.tm_isdst) tzone -= 3600;
  
  char st[26];
  strftime(st, 26, "%a, %d %b %Y %H:%M:%S", &tlocal);

  char tz[26];
  strftime(tz, 26, "%H%M", gmtime(&tzone));

  char str[64];
  sprintf(str, "%s %c%s", st, tzsign, tz);

  return std::string(str);
}

int
socklib::duration2seconds(const char* duration)
{
  // duration is of the following format
  // duration := week | day | hour | min | sec
  // week     := nn ( "week" | "weeks" | "W" ) day{0,1}
  // day      := nn ( "day" | "days" | "D" ) hour{0,1}
  // hour     := nn ( "hour" | "hours" | "H" ) min{0,1}
  // min      := nn ( "min" | "mins" | "minute" | "minutes" | "M" ) sec{0,1}
  // sec      := nn ( "sec" | "secs" | "second" | "seconds" | "S" )
  // nn       := [0-9]+

  enum parse_state {
    looking_for_space,
    looking_for_number,
    parsing_number,
    looking_for_unit,
    parsing_unit,
  };

  int    dur            = 0;
  int    val            = 0;
  const char* saved_pos = duration;
  parse_state state     = looking_for_number;

  while(*duration) {
    int  c = *duration++;

    switch(state) {
    case looking_for_space:
      if (isspace(c)) {
        val = 0;
        state = looking_for_number;
      }
      break;

    case looking_for_number:
      if (isdigit(c)) {
        state = parsing_number;
        val = c - '0';
      }
      break;

    case parsing_number:
      if (isdigit(c))
        val = val*10 + c - '0';
      else if (isalpha(c)) {
        duration--;
        state = looking_for_unit;
      } else
        state = looking_for_unit;
      break;

    case looking_for_unit:
      if (isalpha(c)) {
        state = parsing_unit;
        saved_pos = duration-1;
      }
      break;

    case parsing_unit:
      if (isdigit(c))
        duration--;
        
      if (!isalpha(c)) {
        state = looking_for_space;
        switch(tolower(*saved_pos)) {
        case 'w': dur += val * 7 * 24 * 3600; break;
        case 'd': dur += val * 24 * 3600; break;
        case 'h': dur += val * 3600; break;
        case 'm': dur += val * 60; break;
        default: dur += val;
        }
        val = 0;
        state = looking_for_number;
        saved_pos = "";
      }        
      break;
    }
  }

  if (state == parsing_unit) {
    switch(tolower(*saved_pos)) {
    case 'w': dur += val * 7 * 24 * 3600; break;
    case 'd': dur += val * 24 * 3600; break;
    case 'h': dur += val * 3600; break;
    case 'm': dur += val * 60; break;
    default: dur += val;
    }

    return dur;
  }

  dur += val;

  return dur;
}

std::string
socklib::seconds2duration(int secs)
{
  // convert seconds to nWnDnHnMnS
  std::string dur("");
  char buf[8192];

  int w = secs / (7 * 24 * 3600);
  secs %= 7 * 24 * 3600;

  if (w) {
    _snprintf(buf, 8192, "%dW", w);
    dur = dur + buf;
  }

  int d = secs / (24 * 3600);
  secs %= 24 * 3600;

  if (d) {
    _snprintf(buf, 8192, "%dD", d);
    dur = dur + buf;
  }

  int h = secs / 3600;
  secs %= 3600;

  if (h) {
    _snprintf(buf, 8192, "%dH", h);
    dur = dur + buf;
  }

  int m = secs / 60;
  secs %= 60;

  if (m) {
    _snprintf(buf, 8192, "%dM", m);
    dur = dur + buf;
  }

  if (secs) {
    _snprintf(buf, 8192, "%dS", secs);
    dur = dur + buf;
  }

  if (dur.size() == 0)
    dur = "0S";

  return dur;
}

void
socklib::trim(char* str)
{
  char* e = str + strlen(str);
  while(e-- > str && isspace(*e)) *e = 0;

  char* p = str;
  while(*p && isspace(*p)) p++;
  
  if (p != str) {
    e = p + strlen(p);
    memmove(str, p, e-p+1);
  }
}

void
socklib::trim(std::string& str)
{
  std::string ostr;

  const char* s = str.c_str();
  const char* e = s + str.size();

  while(*s && isspace(*s)) s++;
  while(e-- > s && isspace(*e));

  ostr.assign(s, ++e);

  str = ostr;
}